package pkg.andru.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.Scroller;

public class FlipViewPager extends ViewGroup {

    static final String LOG_TAG = "FlipViewPager";

    private static final int SNAP_VELOCITY = 600;
    private final static int TOUCH_STATE_REST = 0;
    private final static int TOUCH_STATE_SCROLLING = 1;

    private static final float BASELINE_FLING_VELOCITY = 2500.f;
    private static final float FLING_VELOCITY_INFLUENCE = 0.8f;
    private static final float SMOOTHING_SPEED = 1.00f;
    private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED));
    private static final float NANOTIME_DIV = 1000000000.0f;
    private static final int BOUNCING_WIDTH = 200;

    private int mMaximumVelocity;
    private float mTouchX;
    private float mSmoothingTime;
    private int screen_width;
    private SlideListener sListener;
    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;
    private WorkspaceOvershootInterpolator mScrollInterpolator;

    private int mScrollX = 0;
    private int mCurrentScreen = 0;

    private float mLastMotionX;

    private int mTouchState;

    private int mTouchSlop = 0;

    boolean isBouncing = true;

    public FlipViewPager(Context context) {
        this(context, null);
    }

    public FlipViewPager(Context context, AttributeSet attrs) {
        this(context, attrs, 0);

    }

    public FlipViewPager(Context context, AttributeSet attrs, int theme) {
        super(context, attrs, theme);

        mScrollInterpolator = new WorkspaceOvershootInterpolator();
        mScroller = new Scroller(context, mScrollInterpolator);

        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mCurrentScreen = 0;

        final ViewConfiguration configuration = ViewConfiguration.get(context);
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    }

    private static class WorkspaceOvershootInterpolator implements Interpolator {
        private static final float DEFAULT_TENSION = 0.3f;
        private float mTension;

        public WorkspaceOvershootInterpolator() {
            mTension = DEFAULT_TENSION;
        }

        public void setDistance(int distance) {
            mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
        }

        public void disableSettle() {
            mTension = 0.f;
        }

        public float getInterpolation(float t) {
            // _o(t) = t * t * ((tension + 1) * t + tension)
            // o(t) = _o(t - 1) + 1
            t -= 1.0f;
            return t * t * ((mTension + 1) * t + mTension) + 1.0f;
        }
    }

    public int getScreen_width() {
        return screen_width;
    }

    public void setScreen_width(int screenWidth) {
        screen_width = screenWidth;
    }

    public void setsListener(SlideListener sListener) {
        this.sListener = sListener;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        int action = event.getAction();
        if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
            return true;
        }

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);

        final float px = event.getX();

        switch (action & MotionEvent.ACTION_MASK) {

        case MotionEvent.ACTION_DOWN:
            mLastMotionX = px;
            mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
            break;

        case MotionEvent.ACTION_MOVE:
            boolean isMoved = Math.abs(px - mLastMotionX) > mTouchSlop;

            if (isMoved) {
                // Scroll if the user moved far enough along the X axis
                mTouchX = mScrollX;
                mTouchState = TOUCH_STATE_SCROLLING;
                mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
            }
            break;

        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            mTouchState = TOUCH_STATE_REST;
            break;
        }

        return mTouchState != TOUCH_STATE_REST;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        final int action = event.getAction();
        final float px = event.getX();

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);

        switch (action) {
        case MotionEvent.ACTION_DOWN:
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }

            mLastMotionX = px;
            break;
        case MotionEvent.ACTION_MOVE:
            mTouchState = TOUCH_STATE_SCROLLING;

            // Scroll to follow the motion event
            int deltaX = (int) (mLastMotionX - px);
            mLastMotionX = px;

            if (deltaX < 0) { // move ->
                if (mScrollX > 0) {
                    mTouchX += Math.max(-mTouchX, deltaX);
                    mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
                    invalidate();
                }
                if (isBouncing && mCurrentScreen == 0) {
                    leftBouncing();
                    isBouncing = false;
                }
            } else if (deltaX > 0) {
                if (isBouncing && mCurrentScreen == getChildCount() - 1) {
                    rightBouncing();
                    isBouncing = false;
                }
                View viewTemp = getChildAt(getChildCount() - 1);
                if (viewTemp == null)
                    break;
                final float availableToScroll = viewTemp.getRight() - mTouchX - screen_width;
                if (availableToScroll > 0) {
                    mTouchX += Math.min(availableToScroll, deltaX);
                    mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
                    // scrollBy(Math.min((int)availableToScroll, deltaX), 0);
                    invalidate();
                }
            } else {
                awakenScrollBars();
            }

            break;
        case MotionEvent.ACTION_UP:
            if (mTouchState == TOUCH_STATE_SCROLLING) {
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int velocityX = (int) velocityTracker.getXVelocity();

                if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
                    // Fling hard enough to move left
                    snapToScreen(mCurrentScreen - 1, velocityX, true);
                    // snapToDestination();
                } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
                    // Fling hard enough to move right
                    snapToScreen(mCurrentScreen + 1, velocityX, true);
                    // snapToDestination();
                } else {
                    snapToDestination();
                    isBouncing = true;
                }

                // setChildrenDrawnWithCacheEnabled(false);

                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
            }
            mTouchState = TOUCH_STATE_REST;
            break;
        case MotionEvent.ACTION_CANCEL:
            // setChildrenDrawnWithCacheEnabled(false);
            mTouchState = TOUCH_STATE_REST;
            break;
        }
        // mScrollX = this.getScrollX();

        return true;
    }

    private void snapToDestination() {
        final int screenWidth = screen_width;
        if (screenWidth == 0)
            return;
        final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
        snapToScreen(whichScreen, 0, true);
    }

    public void rightBouncing() {
        int delta = BOUNCING_WIDTH;
        mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);
        invalidate();
    }

    public void leftBouncing() {
        int delta = -BOUNCING_WIDTH;
        mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);
        invalidate();
    }

    public void snapToScreen(int whichScreen, int velocity, boolean settle) {
        mCurrentScreen = whichScreen;
        // setChildrenDrawnWithCacheEnabled(true);
        // setChildrenDrawingCacheEnabled(true);

        View focusedChild = getFocusedChild();
        if (focusedChild != null && whichScreen != mCurrentScreen && focusedChild == getChildAt(mCurrentScreen)) {
            focusedChild.clearFocus();
        }

        final int newX = whichScreen * screen_width;
        final int delta = newX - mScrollX;
        final int screenDelta = Math.max(1, Math.abs(whichScreen - mCurrentScreen));
        int duration = (screenDelta + 1) * 100;
        if (!mScroller.isFinished()) {
            mScroller.abortAnimation();
        }

        if (settle) {
            mScrollInterpolator.setDistance(screenDelta);
        } else {
            mScrollInterpolator.disableSettle();
        }

        velocity = Math.abs(velocity);
        if (velocity > 0) {
            duration += (duration / (velocity / BASELINE_FLING_VELOCITY)) * FLING_VELOCITY_INFLUENCE;
        } else {
            duration += 100;
        }

        awakenScrollBars(duration);

        mScroller.startScroll(mScrollX, 0, delta, 0, duration);
        invalidate();
        sListener.onComplete(whichScreen);
    }

    public void setToScreen(int whichScreen) {
        mCurrentScreen = whichScreen;
        final int newX = whichScreen * screen_width;
        mScroller.startScroll(newX, 0, 0, 0, 10);
        invalidate();
        sListener.onComplete(whichScreen);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childLeft = 0;

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                final int childWidth = child.getMeasuredWidth();
                child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
                childLeft += childWidth;
            }
        }

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        final int width = MeasureSpec.getSize(widthMeasureSpec);
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        if (widthMode != MeasureSpec.EXACTLY) {
            throw new IllegalStateException("error mode.");
        }

        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode != MeasureSpec.EXACTLY) {
            throw new IllegalStateException("error mode.");
        }

        // The children are given the same width and height as the workspace
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
        }
        scrollTo(mCurrentScreen * width, 0);
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mTouchX = mScrollX = mScroller.getCurrX();
            mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
            scrollTo(mScrollX, 0);
            postInvalidate();
        } else if (mTouchState == TOUCH_STATE_SCROLLING) {
            final float now = System.nanoTime() / NANOTIME_DIV;
            final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT);
            final float dx = mTouchX - mScrollX;
            mScrollX += dx * e;
            mSmoothingTime = now;
            scrollTo(mScrollX, 0);
            // Keep generating points as long as we're more than 1px away from
            // the target
            if (dx > 1.f || dx < -1.f) {

                postInvalidate();
            }

        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        boolean restore = false;
        int restoreCount = 0;

        // ViewGroup.dispatchDraw() supports many features we don't need:
        // clip to padding, layout animation, animation listener, disappearing
        // children, etc. The following implementation attempts to fast-track
        // the drawing dispatch by drawing only what we know needs to be drawn.

        boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING;
        // If we are not scrolling or flinging, draw only the current screen
        if (fastDraw) {
            View view = getChildAt(mCurrentScreen);
            if (view == null)
                return;
            drawChild(canvas, view, getDrawingTime());
        } else {
            final long drawingTime = getDrawingTime();
            final float scrollPos = (float) mScrollX / getWidth();
            final int leftScreen = (int) scrollPos;
            final int rightScreen = leftScreen + 1;
            if (leftScreen >= 0) {
                View view = getChildAt(leftScreen);
                if (view == null)
                    return;
                drawChild(canvas, getChildAt(leftScreen), drawingTime);
            }
            if (scrollPos != leftScreen && rightScreen < getChildCount()) {
                View view = getChildAt(rightScreen);
                if (view == null)
                    return;
                drawChild(canvas, getChildAt(rightScreen), drawingTime);
            }
        }

        if (restore) {
            canvas.restoreToCount(restoreCount);
        }

    }

    @Override
    public void scrollTo(int x, int y) {
        super.scrollTo(x, y);
        mTouchX = x;
        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
    }

    public interface SlideListener {
        public void onComplete(int index);
    }
}